package group6renderer;

/**
 * Main display class that draws the document to the screen.
 * 
 * COLLABORATORS: State, GUI, Renderer
 * 
 * 
 * @author Joe Thomas, Jonathan Davies
 * @version 0.2
 */
import group6renderer.renderernode.TableCellNode;
import group6renderer.renderernode.TreeNode;
import java.util.HashMap;
import java.awt.*;
import java.awt.font.TextAttribute;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.EtchedBorder;

public class Display extends JComponent
{
    // constants.

    public static Color FOREGROUND_COLOR = Color.BLACK;
    public static String DEFAULT_FONT = "Tahoma";
    private static final String MONOSPACED_FONT = "Courier New";
    public static float TEXT_SIZE = 14;
    private static final int LEFT_MARGIN = 10;
    private static final int TOP_MARGIN = 15;
    private static final int RIGHT_MARGIN = 20;
    private static final int LINE_SPACING = 2;
    private State state;
    public static String search = "";
    private int y;
    private int x;
    private int width;
    private int spaceleft;
    private Graphics2D g2;
    private TreeNode theRoot;
    private static int INDENT_AMOUNT = 0;
    private JPanel theTable;
    private JPanel theRow;
    private Display theCell = this;
    private String heading;
    private String lastHeading="";
    private String listIndex="";
    private String title="";
    
    /**
     * A Constructor to initialise the instance variables of a new Display.
     * @param theRenderer 
     */
    public Display(TreeNode theRoot)
    {
	state = new State();
	this.theRoot = theRoot;
	setLayout(null);
    }

    /**
     * Starts drawing a table
     */
    public void openTable()
    {
	crlf();
    	theTable = new JPanel();
	theTable.setLayout(new BoxLayout(theTable, BoxLayout.Y_AXIS));
	theTable.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	theTable.setBounds(x, y, width - LEFT_MARGIN - RIGHT_MARGIN, 400);
	
	add(theTable);
    }
    
    /**
     * Starts drawing a table row
     */
    public void openTableRow()
    {
	theRow = new JPanel();
	theRow.setLayout(new BoxLayout(theRow, BoxLayout.X_AXIS));
	theTable.add(theRow);
	
    }
    
    /**
     * Starts drawing a table cell
     * @param theCell, the treenode to put in the cell
     */
    public void openTableCell(TreeNode theCell)
    {
	JPanel gl = new JPanel();
	gl.setLayout(new GridLayout(1,1));
	this.theCell = new Display(theCell);
	gl.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
	gl.add(this.theCell);
	theRow.add(gl);
	validate();
	
    }
    
    /**
     * Closes a table
     */
    public void closeTable()
    {
	y += theTable.getHeight();
	crlf();
    }
   
    
    /**
     * Overrides the paint componenent method of Component. All the logic of the renderer is called from here, meaning the document is re-rendered every time the window is resized or redrawn in any way
     * @param g
     */
    @Override
    public void paintComponent(Graphics g)
    {
	removeAll();
	y = TOP_MARGIN;
	x = LEFT_MARGIN;
	width = getParent().getWidth() - RIGHT_MARGIN;
	spaceleft = width;
	g2 = (Graphics2D) g;
	g2.clearRect(0, 0, getWidth(), getHeight());
	state = new State();
	if(theRoot instanceof TableCellNode)
	{
	    Group6Renderer.theRenderer.renderTable((TableCellNode) theRoot, this);
	}
	else
	{
	    Group6Renderer.theRenderer.render(theRoot, this);
	}
	g2.dispose();
    }

    /**
     * The primary method of the class. It takes a String of text (the next part of text to be rendered, aquires the various formatting states from the State class and writes
     * the text to this display with these states.
     * It wraps the text word by word using a simple greedy algorithm. It is painted line by line however. This is needed for centering.
     * @param text the text to be formatted to the display.
     */
    public void addText(String text)
    {
	// this first section is used to compose a HashMap of all the formatting attributes the text font should have/

	HashMap map = new HashMap();

	heading = state.getHeading();
	if (!heading.isEmpty())
		{
		lastHeading="";
		lastHeading=lastHeading.copyValueOf(heading.toCharArray());
		}
	
	listIndex= state.getListIndex();
	
	map.put(TextAttribute.FAMILY, DEFAULT_FONT);
	map.put(TextAttribute.SIZE, TEXT_SIZE);

	
	if (state.getState(HTMLConstants.PRE) || state.getState(HTMLConstants.TTFONT))
	{
	    map.put(TextAttribute.FAMILY, MONOSPACED_FONT);
	}
	else
	{
	    map.put(TextAttribute.FAMILY, DEFAULT_FONT);
	}
	if (state.getState(HTMLConstants.BOLD))
	{
	    map.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); // add the bold attribute.

	}
	if (state.getState(HTMLConstants.ITALIC))
	{
	    map.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); // add the italic attribute.

	}
	if (state.getState(HTMLConstants.UNDERLINE))
	{
	    map.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); // add the underlined attribute.

	}

	Font stateFont = new Font(map); // create a new font the text will have by using the map of text attributes.

	
	g2.setFont(stateFont);
	
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

	String line = heading + listIndex + text;
	FontMetrics fm = g2.getFontMetrics();
	setIndent();
	line = line.replaceAll("\n", " \n ");
	String[] words = line.split(" ");
	ArrayList<StringBuffer> sbs = new ArrayList<StringBuffer>();
	sbs.add(new StringBuffer());
	//word wraps the text into multiple lines, puts these into the arraylist of stringbuffers sbs
	for (String s : words)
	{
	    if ((fm.stringWidth(s) > (spaceleft - INDENT_AMOUNT) && !state.getState(HTMLConstants.PRE)) || (state.getState(HTMLConstants.PRE) && (s.equals("\n"))))
	    {
		spaceleft = width - fm.stringWidth(s);
		sbs.add(new StringBuffer());
	    }
	    else
	    {
		spaceleft -= (fm.stringWidth(s) + fm.stringWidth(" "));
	    }
	    if (state.getState(HTMLConstants.PRE) && s.equals(""))
	    {
		sbs.get(sbs.size() - 1).append(" ");
	    }
	    else if (!s.equals(""))
	    {
		sbs.get(sbs.size() - 1).append(s + " ");
	    }

	}
	//draws each line to the screen, in red if it matches the search string
	for (int i = 0; i < sbs.size(); i++)
	{
	    if(sbs.get(i).toString().contains(search) && search.length() > 0)
	    {
		g2.setColor(Color.RED);
	    }
	    else
	    {
		g2.setColor(FOREGROUND_COLOR);
	    }
	    x += INDENT_AMOUNT;
	    if (state.getState(HTMLConstants.CENTER))
	    {
		x = (width - fm.stringWidth(sbs.get(i).toString())) / 2;
	    }
	    if (!sbs.get(i).toString().equals(""))
	    {
		g2.drawString(sbs.get(i).toString(), x, y);
	    }

	    if (i < sbs.size() - 1)
	    {
		y += fm.getHeight() + LINE_SPACING;
		x = LEFT_MARGIN;
		setHeadingIndent();
	    }
	    else
	    {
		x += fm.stringWidth(sbs.get(i).toString());
	    }
	    //resizes the component to fit.
	    int theWidth = fm.stringWidth(sbs.get(i).toString()) + LEFT_MARGIN + RIGHT_MARGIN + INDENT_AMOUNT;
	    if (getWidth() < theWidth && state.getState(HTMLConstants.PRE))
	    {
		setPreferredSize(new Dimension(theWidth, y));
	    }
	    else
	    {
		setPreferredSize(new Dimension((int) getPreferredSize().getWidth(), y));
	    }
	}

    }

    /**
     * A method to write an image to the display using a pathname to create an ImageIcon which is inside a JLabel.
     * @param image 
     * @param filePath the file path of the image to be displayed.
     */
    public void addImage(BufferedImage image)
    {
	setIndent();
    	crlf();
	g2.drawImage(image, x + INDENT_AMOUNT, y, null);
	y += image.getHeight();
	crlf();
	int theWidth = image.getWidth() + LEFT_MARGIN + RIGHT_MARGIN + INDENT_AMOUNT;
	    if (getWidth() < theWidth)
	    {
		setPreferredSize(new Dimension(theWidth, y));
	    }
	    else
	    {
		setPreferredSize(new Dimension((int) getPreferredSize().getWidth(), y));
	    }
    }
    
    /**
     * Draws a horizontal line
     */
    public void addLine()
    {
	g2.drawLine(LEFT_MARGIN, y, width - LEFT_MARGIN, y);
    }

    /**
     * does a line feed carrage return
     */
    public void crlf()
    {
	y += g2.getFontMetrics().getHeight() + LINE_SPACING;
	x = LEFT_MARGIN;
	spaceleft = width;
    }

    /**
     * A method to return the state variable should external classes need to reference it.
     * @return the current state.
     */
    public State getState()
    {
	return state;
    }

    /**
     * Sets the indent amount according to the state.
     */
    private void setIndent()
    {
	if (state.getState(HTMLConstants.BLOCKQUOTE) || state.getState(HTMLConstants.DEFINITION_DATA))
	{
	    INDENT_AMOUNT = g2.getFontMetrics().stringWidth("         ");
	}
	
	
	
	else
	{
	    INDENT_AMOUNT = 0;
	}
        
    }
    
    /**
     * sets the indent amount variable for the heading condition
     */
    public void setHeadingIndent()
    {
    	if (state.getState(HTMLConstants.HEADING) )
    		INDENT_AMOUNT =g2.getFontMetrics().stringWidth(lastHeading);
    	
    	if (state.getState(HTMLConstants.LISTINDEX))	
    		INDENT_AMOUNT =g2.getFontMetrics().stringWidth((listIndex));

    }
    
    /**
     * Sets the title 
     * @param title
     */
    public void setHtmlTitle(String title)
    {
    	this.title=title;
    }

    /**
     * Gets the title
     * @return the title
     */
    public String getHtmlTitle()
    {
    	return title;
    }
}

